use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
use proc_macro2::TokenStream as T2;
use crate::utils::{struct_all_types, Ty, TypeInfo};



impl TypeInfo {

    fn d_build(&self, check_flag: bool) -> T2 {

        let sc = match &self.ty {
            Ty::U8 => self.d_build_u8(),
            Ty::I8 => self.d_build_i8(),
            Ty::U16 => self.d_build_u16(),
            Ty::I16 => self.d_build_i16(),
            Ty::U32 => self.d_build_u32(),
            Ty::I32 => self.d_build_i32(),
            Ty::BYTES => self.d_build_bytes(),
        };
        if !check_flag {
            return sc;
        }
        let ty_size = self.ty.size();
        let check = quote! {
            if buf.len() < (index + #ty_size) {
                return None;
            }
            #sc
        };
        check
    }

    fn d_build_u8(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe { *buf.get_unchecked(index) };
            index += 1;
        }
    }

    fn d_build_i8(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe { *buf.get_unchecked(index) as i8 };
            index += 1;
        }
    }

    fn d_build_u16(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe {
                ((*buf.get_unchecked(index) as u16) << 8) |
                ((*buf.get_unchecked(index + 1) as u16))
            };
            index += 2;
        }
    }

    fn d_build_i16(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe {
                ((*buf.get_unchecked(index) as i16) << 8) |
                ((*buf.get_unchecked(index + 1) as i16))
            };
            index += 2;
        }
    }

    fn d_build_u32(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe {
                ((*buf.get_unchecked(index) as u32) << 24) |
                ((*buf.get_unchecked(index + 1) as u32) << 16) |
                ((*buf.get_unchecked(index + 2) as u32) << 8) |
                ((*buf.get_unchecked(index + 3) as u32))
            };
            index += 4;
        }
    }

    fn d_build_i32(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        quote! {
            let #name = unsafe {
                ((*buf.get_unchecked(index) as i32) << 24) |
                ((*buf.get_unchecked(index + 1) as i32) << 16) |
                ((*buf.get_unchecked(index + 2) as i32) << 8) |
                ((*buf.get_unchecked(index + 3) as i32))
            };
            index += 4;
        }
    }

    fn d_build_bytes(&self) -> T2 {
        let name = format_ident!("temp{}", &self.name);
        let name_len = format_ident!("{}len", name);
        
        quote! {
            let #name_len = unsafe {
                *buf.get_unchecked(index) as usize
            };
            if buf.len() < (index + 1 + #name_len) {
                return None;
            }
            let #name = unsafe {
                ::core::slice::from_raw_parts(buf.as_ptr().add(index + 1), #name_len)
            };
            index += 1 + #name_len;
        }
    }

}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let (_impl_life, type_life, _) = input.generics.split_for_impl();
    let lifes: Vec<_> = input.generics.lifetimes().collect();

    let fields = struct_all_types(&input);

    let mut min_size: usize = 0;
    for field in fields.iter() {
        min_size += field.ty.size();
    }

    let mut builds = quote! {};
    let mut check_flag = false;
    let mut ty_names = Vec::new();
    for field in fields.iter() {
        builds.extend(field.d_build(check_flag));
        if field.is_bytes() {
            check_flag = true;
        }
        let name = &field.name;
        let f_name = format_ident!("temp{}", &field.name);
        ty_names.push( quote! {
            #name: #f_name
        } );
    }

    let expand = quote! {

        impl <#(#lifes,)* 'de: #(#lifes+)*> Decode<'de> for #name #type_life {

            const DECODE_MIN_LEN: usize = #min_size;

            fn from_bytes(buf: &'de [u8]) -> Option<Self> {
                if buf.len() < #min_size {
                    return None;
                }
                let mut index = 0;
                #builds

                Some(#name { #(#ty_names,)* })
            }
        }
    };
 
    expand.into()
}






